Skip to content

[17.0][FIX] account_invoice_facturx: fix Factur-X 4.x schematron errors#1

Open
tendil wants to merge 2 commits into17.0from
17.0-t5347-account_invoice_facturx-fix_factur_4_validation
Open

[17.0][FIX] account_invoice_facturx: fix Factur-X 4.x schematron errors#1
tendil wants to merge 2 commits into17.0from
17.0-t5347-account_invoice_facturx-fix_factur_4_validation

Conversation

@tendil
Copy link
Copy Markdown
Collaborator

@tendil tendil commented Apr 7, 2026

With factur-x 4.x, schematron validation became stricter and the module
started generating EN16931 XML that no longer passed validation for
credit transfer invoices.

Two concrete problems caused that failure:

  • the seller contact email was exported with a schemeID attribute on
    URIID, although this attribute is not allowed in that context;
  • when the creditor account was not an IBAN, the module did not export
    a fallback account identifier, so the payment means block failed
    BR-CO-27.

As a result, invoices that were previously generated successfully with
factur-x 3.x started failing during Factur-X XML generation with
factur-x 4.x.


This patch fixes the XML generation logic by:

  • removing the invalid schemeID attribute from the seller email URIID;
  • exporting ProprietaryID for non-IBAN creditor accounts;
  • raising a clear UserError when no account identifier is available,
    instead of generating XML that is known to be invalid.

The test suite is updated accordingly:

  • the default invoice fixture now includes a valid creditor account;
  • a regression test checks that URIID is generated without schemeID;
  • a regression test checks that non-IBAN accounts are exported as
    ProprietaryID;
  • a regression test checks that missing account identifiers are rejected
    explicitly.

Task: 5347

Summary by CodeRabbit

  • Bug Fixes

    • Stronger validation for creditor bank details during Factur‑X generation; missing or invalid account now raises a clear invoice-specific error.
    • Factur‑X XML: seller email element no longer includes a schemeID attribute; account identifier emits IBAN for IBAN-type accounts or a proprietary identifier otherwise, with the account value always populated.
    • Delivery date is now included in the delivery event when the selected profile requires it.
  • Tests

    • Added tests for updated XML structure, seller email behavior, delivery-date emission, and the new bank-account validation error; XSD validation is mocked in tests.

@CetmixGitDrone
Copy link
Copy Markdown

Hi @alexis-via,
some modules you are maintaining are being modified, check this out!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e046cafc-4d0f-4e9f-ac47-bfa65cfd6791

📥 Commits

Reviewing files that changed from the base of the PR and between 6a436c4 and 7479b80.

📒 Files selected for processing (1)
  • account_invoice_facturx/models/account_move.py

Walkthrough

Seller email ram:URIID no longer includes schemeID="SMTP". Added _cii_get_delivery_date() and conditional ActualDeliverySupplyChainEvent emission for certain profiles. Credit-transfer path now validates partner_bank and sanitized_acc_number (raises UserError), always emits PayeePartyCreditorFinancialAccount and uses ram:IBANID for IBAN or ram:ProprietaryID otherwise; BIC remains conditional.

Changes

Cohort / File(s) Summary
Model logic
account_invoice_facturx/models/account_move.py
Added _cii_get_delivery_date(); removed schemeID="SMTP" from seller ram:URIID; emit ram:ActualDeliverySupplyChainEvent/OccurrenceDateTime for EN profiles when delivery date present; credit-transfer path now raises UserError if partner_bank missing or missing sanitized_acc_number; always create ram:PayeePartyCreditorFinancialAccount and emit ram:IBANID when partner_bank.acc_type == "iban" else ram:ProprietaryID with identifier set to sanitized_acc_number; conditional ram:BICID remains and is emitted after identifier selection.
Tests
account_invoice_facturx/tests/test_facturx_invoice.py
Added RAM_NS/NSMAP, patched xml_check_xsd, new _generate_xml_root() helper, ensure invoices have res.partner.bank in setup/posting, and added tests asserting absence of schemeID on seller ram:URIID, presence of ram:ProprietaryID (no ram:IBANID) for proprietary bank, and that clearing partner_bank_id on a copied posted invoice raises UserError and prevents XSD check call.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: addressing Factur-X 4.x schematron validation errors in the account_invoice_facturx module, which matches the core changes across both modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@account_invoice_facturx/tests/test_facturx_invoice.py`:
- Line 8: The test currently uses assertRaises(UserError) which is too broad;
tighten it to prove the error is raised by the new pre-validation path of
generate_facturx_xml rather than later xml_check_xsd. Replace the generic
assertRaises with an assertRaisesRegex(UserError, "missing.*account") that
matches the exact pre-validation message produced by xml_pre_validate, and/or
patch/mock xml_check_xsd inside the test so it would raise a different sentinel
or assert it was not called; target the generate_facturx_xml() call in
test_facturx_invoice.py and references to xml_pre_validate/xml_check_xsd to
ensure the failure originates from the early guard.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc16d03d-dcd0-4141-a3dd-7b6cfdeffe8a

📥 Commits

Reviewing files that changed from the base of the PR and between 543218a and 835698f.

📒 Files selected for processing (2)
  • account_invoice_facturx/models/account_move.py
  • account_invoice_facturx/tests/test_facturx_invoice.py

Comment thread account_invoice_facturx/tests/test_facturx_invoice.py
@tendil tendil changed the title [FIX] account_invoice_facturx: fix Factur-X 4.x schematron errors [17.0][FIX] account_invoice_facturx: fix Factur-X 4.x schematron errors Apr 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
account_invoice_facturx/tests/test_facturx_invoice.py (1)

160-162: ⚠️ Potential issue | 🟡 Minor

Tighten the missing-account test to prove the pre-validation failure path.

At Line 161, assertRaises(UserError) is still broad and can pass on downstream failures. Pin it to the explicit missing-account error and/or assert that XSD validation is not reached.

Suggested test hardening
+from unittest.mock import patch
@@
-        with self.assertRaises(UserError):
-            invoice.generate_facturx_xml()
+        with patch(
+            "odoo.addons.account_invoice_facturx.models.account_move.xml_check_xsd"
+        ) as xml_check_xsd:
+            with self.assertRaisesRegex(
+                UserError, "Missing bank account identifier on invoice"
+            ):
+                invoice.generate_facturx_xml()
+            xml_check_xsd.assert_not_called()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@account_invoice_facturx/tests/test_facturx_invoice.py` around lines 160 -
162, The test currently uses a broad assertRaises(UserError) on
invoice.generate_facturx_xml(); tighten it to assert the specific
missing-account error (e.g. use self.assertRaisesRegex(UserError,
r"missing.*account") when calling invoice.generate_facturx_xml()) and
additionally assert that the XSD validation path is not reached by
spying/mocking the XSD validation helper (e.g. patch or mock the module/class
method used for final validation such as validate_facturx_xml or _validate_xsd
and assert it was not called). Ensure you reference
invoice.generate_facturx_xml() and the
company.write({"facturx_level":"en16931"}) setup in the updated assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@account_invoice_facturx/tests/test_facturx_invoice.py`:
- Around line 160-162: The test currently uses a broad assertRaises(UserError)
on invoice.generate_facturx_xml(); tighten it to assert the specific
missing-account error (e.g. use self.assertRaisesRegex(UserError,
r"missing.*account") when calling invoice.generate_facturx_xml()) and
additionally assert that the XSD validation path is not reached by
spying/mocking the XSD validation helper (e.g. patch or mock the module/class
method used for final validation such as validate_facturx_xml or _validate_xsd
and assert it was not called). Ensure you reference
invoice.generate_facturx_xml() and the
company.write({"facturx_level":"en16931"}) setup in the updated assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a1662e4-de53-4b17-83e7-5b8fbf71fe8f

📥 Commits

Reviewing files that changed from the base of the PR and between 835698f and de4453e.

📒 Files selected for processing (1)
  • account_invoice_facturx/tests/test_facturx_invoice.py

@tendil tendil force-pushed the 17.0-t5347-account_invoice_facturx-fix_factur_4_validation branch from de4453e to a6d2250 Compare April 8, 2026 07:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@account_invoice_facturx/tests/test_facturx_invoice.py`:
- Line 81: Redundant reassignment: remove the post-posting line that sets
cls.invoice.partner_bank_id = cls.proprietary_bank in the test (it's already
assigned at invoice creation using cls.proprietary_bank.id), so delete that
statement (or its equivalent) after calling action_post() and run the tests to
confirm no behavior change; reference symbols: partner_bank_id,
cls.proprietary_bank, action_post, and the test_facturx_invoice creation flow
where partner_bank_id is originally set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fee23927-56c0-4fc8-800b-3bca4835fed9

📥 Commits

Reviewing files that changed from the base of the PR and between de4453e and a6d2250.

📒 Files selected for processing (1)
  • account_invoice_facturx/tests/test_facturx_invoice.py

Comment thread account_invoice_facturx/tests/test_facturx_invoice.py
Copy link
Copy Markdown

@ivs-cetmix ivs-cetmix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review LGTM

Prevent Factur-X invoices from failing schematron validation with
factur-x 4.x.

The generated XML must stay compliant with newer validation rules,
otherwise valid Odoo invoices cannot be exported as Factur-X documents.
This change removes the invalid email URI attribute, exports a
proprietary account identifier when IBAN is not available, and updates
tests to cover the new compliant output and the missing-account error.

Task: 5347
@tendil tendil force-pushed the 17.0-t5347-account_invoice_facturx-fix_factur_4_validation branch from a6d2250 to 6a436c4 Compare April 8, 2026 08:23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@account_invoice_facturx/tests/test_facturx_invoice.py`:
- Around line 159-160: The test currently clears partner_bank_id only via
copy(default={"partner_bank_id": False}) but action_post() can recompute and
restore it; after creating the invoice (variable invoice) and before/after
calling invoice.action_post() explicitly ensure invoice.partner_bank_id is set
to False (i.e., assign False to the partner_bank_id field on the account.move
instance) so the missing-account scenario is preserved; locate the usage around
invoice.copy(...) and invoice.action_post() and add a direct assignment to
partner_bank_id on that invoice instance to clear it again.
- Around line 133-157: Add a complementary test to cover the IBAN branch: create
an XML root using the IBAN fixture (e.g., mirror
test_credit_transfer_uses_proprietary_id_for_non_iban_account but using the IBAN
bank setup such as self.iban_bank), query the same XPath expressions (ram:IBANID
and ram:ProprietaryID using NSMAP), assert that ram:IBANID nodes are present and
ram:IBANID.text equals the expected sanitized IBAN value, and assert that
ram:ProprietaryID is not present for the IBAN case (use the same variables
xml_root, iban_nodes, proprietary_nodes to keep consistency).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5f9fa4d5-4cc1-4f41-adbd-f0abfae01ddf

📥 Commits

Reviewing files that changed from the base of the PR and between a6d2250 and 6a436c4.

📒 Files selected for processing (2)
  • account_invoice_facturx/models/account_move.py
  • account_invoice_facturx/tests/test_facturx_invoice.py

Comment on lines +133 to +157
def test_credit_transfer_uses_proprietary_id_for_non_iban_account(self):
xml_root = self._generate_xml_root(level="en16931")
proprietary_nodes = xml_root.xpath(
"//ram:SpecifiedTradeSettlementPaymentMeans/"
"ram:PayeePartyCreditorFinancialAccount/"
"ram:ProprietaryID",
namespaces=NSMAP,
)
iban_nodes = xml_root.xpath(
"//ram:SpecifiedTradeSettlementPaymentMeans/"
"ram:PayeePartyCreditorFinancialAccount/"
"ram:IBANID",
namespaces=NSMAP,
)

self.assertTrue(
proprietary_nodes,
"Expected ProprietaryID for non-IBAN creditor account",
)
self.assertEqual(
proprietary_nodes[0].text,
self.proprietary_bank.sanitized_acc_number,
)
self.assertFalse(iban_nodes, "IBANID should not be generated for non-IBAN bank")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add a companion regression for the IBAN branch.

This change rewires both sides of the identifier selection, but the new coverage only asserts the non-IBAN path. A small IBAN fixture/assertion would keep ram:IBANID protected too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@account_invoice_facturx/tests/test_facturx_invoice.py` around lines 133 -
157, Add a complementary test to cover the IBAN branch: create an XML root using
the IBAN fixture (e.g., mirror
test_credit_transfer_uses_proprietary_id_for_non_iban_account but using the IBAN
bank setup such as self.iban_bank), query the same XPath expressions (ram:IBANID
and ram:ProprietaryID using NSMAP), assert that ram:IBANID nodes are present and
ram:IBANID.text equals the expected sanitized IBAN value, and assert that
ram:ProprietaryID is not present for the IBAN case (use the same variables
xml_root, iban_nodes, proprietary_nodes to keep consistency).

Comment on lines +159 to +160
invoice = self.invoice.copy(default={"partner_bank_id": False})
invoice.action_post()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear partner_bank_id after posting as well.

action_post() can recompute stored partner_bank_id, so setting it to False only in copy(default=...) does not reliably preserve the missing-account scenario. That makes this regression prone to posting a valid bank back onto the invoice instead of exercising the new early guard.

Suggested tightening
     invoice = self.invoice.copy(default={"partner_bank_id": False})
     invoice.action_post()
+    invoice.partner_bank_id = False

Based on learnings: In Odoo 17, partner_bank_id on account.move is a stored computed field (compute='_compute_partner_bank_id'). Calling action_post() can recompute and overwrite the value set at invoice creation time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@account_invoice_facturx/tests/test_facturx_invoice.py` around lines 159 -
160, The test currently clears partner_bank_id only via
copy(default={"partner_bank_id": False}) but action_post() can recompute and
restore it; after creating the invoice (variable invoice) and before/after
calling invoice.action_post() explicitly ensure invoice.partner_bank_id is set
to False (i.e., assign False to the partner_bank_id field on the account.move
instance) so the missing-account scenario is preserved; locate the usage around
invoice.copy(...) and invoice.action_post() and add a direct assignment to
partner_bank_id on that invoice instance to clear it again.

Some invoices already have a delivery/service date on the Odoo side, but
this date was not exported to the generated Factur-X XML.

As a result, the XML could fail validation because neither a header
delivery date nor a billing period was present.

This patch adds the delivery date to the header trade delivery block by
exporting `ActualDeliverySupplyChainEvent/OccurrenceDateTime`, using the
invoice date as the default delivery/service date.

The XML node order is also kept XSD-compliant.

Task: 5347
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants